﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Collections.Generic;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using System.Threading;
using BrainTechLLC.ThreadSafeObjects;
using System.Collections.ObjectModel;

namespace BrainTechLLC
{
    public static class ListFunctions
    {
        /// <summary>
        /// Returns a new list of items from the original collection that match func(item) == true
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="items"></param>
        /// <param name="fun"></param>
        /// <returns></returns>
        public static List<T> Take<T>(this List<T> items, Func<T, bool> fun)
        {
            List<T> results = new List<T>();
            for (int n = 0; n < items.Count; n++)
            {
                if (fun(items[n]))
                {
                    results.Add(items[n]);
                }
            }
            return results;
        }

        /// <summary>
        /// Returns a new list of items from the original collection that match func(item) == true
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="items"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static List<T> Take<T>(this IEnumerable items, Func<T, bool> func)
        {
            List<T> results = new List<T>();
            foreach (T item in items)
            {
                if (func(item))
                    results.Add(item);
            }
            return results;
        }

#if SILVERLIGHT
#else
        /// <summary>
        /// Performs the specified action on each enum value of the specified enumeration type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="action"></param>
        public static void ForEachEnumValue<T>(this Action<T> action) where T : struct
        {
            Type t = typeof(T);
            var values = Enum.GetValues(t);

            foreach (var o in values)
            {
                action((T)o);
            }
        }
#endif

        /// <summary>
        /// For each attribute in the array, performs the specified action
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="attrs"></param>
        /// <param name="del"></param>
        /// <returns></returns>
        public static T[] ForEachAttribute<T>(this T[] attrs, Action<int, T> action)
        {
            if (attrs != null && attrs.Length > 0)
            {
                for (int n = 0; n < attrs.Length; n++)
                {
                    T attr = attrs[n];
                    action(n, attr);
                }
            }
            return attrs;
        }

        /// <summary>
        /// For each match (defined by match(item) == true), performs the specified action
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="items"></param>
        /// <param name="match"></param>
        /// <param name="action"></param>
        public static void ForEachMatch<T>(this IEnumerable<T> items, Func<T, bool> match, Action<T> action)
        {
            foreach (T item in items)
            {
                if (match(item))
                {
                    action(item);
                }
            }
        }

        /// <summary>
        /// For each match (defined by match(item) == true), performs the specified action
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="items"></param>
        /// <param name="match"></param>
        /// <param name="action"></param>
        public static void ForEachMatch<T>(this List<T> items, Func<T, bool> match, Action<int, T> action)
        {
            for (int n = 0; n < items.Count; n++)
            {
                T item = items[n];

                if (match(item))
                {
                    action(n, item);
                }
            }
        }

        /// <summary>
        /// performs transformation del(item) on each list item and places the result in
        /// the returned array of T2
        /// </summary>
        /// <typeparam name="T1"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <param name="list"></param>
        /// <param name="del"></param>
        /// <returns></returns>
        public static T2[] PlaceInArray<T1, T2>(this List<T1> list, Func<T1, T2> del) where T1 : class
        {
            T2[] results = new T2[list.Count];

            for (int n = 0; n < list.Count; n++)
            {
                T2 res = del(list[n]);
                results[n] = res;
            }

            return results;
        }

        /// <summary>
        /// Returns a new list containing all matching items as determine by func(item) == true
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static List<T> FindMatching<T>(this List<T> list, Func<T, bool> func) where T : class
        {
            List<T> results = new List<T>();
            for (int n = 0; n < list.Count; n++)
            {
                if (func(list[n]))
                {
                    results.Add(list[n]);
                }
            }
            return results;
        }

        /// <summary>
        /// Goes through the list backwards and performs action on each list item
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static List<T> ForEachBackwards<T>(this List<T> list, Action<int, T> action)
        {
            for (int n = list.Count - 1; n >= 0; n--)
            {
                action(n, list[n]);
            }

            return list;
        }

        /// <summary>
        /// Runs through the list and passes each list item to specific 'func'
        /// If the result of func is not null, sets output parameter item to the found item
        /// </summary>
        /// <typeparam name="T1"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <param name="list"></param>
        /// <param name="item"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static T2 FindFirst<T1, T2>(this List<T1> list, out T1 item, Func<T1, T2> func)
            where T1 : class
            where T2 : class
        {
            for (int n = 0; n < list.Count; n++)
            {
                T2 found = func(list[n]);

                if (found != null)
                {
                    item = list[n];
                    return found;
                }
            }
            item = null;
            return null;
        }

        /// <summary>
        /// Determines if the string list contains string s, case insensitive
        /// </summary>
        /// <param name="list"></param>
        /// <param name="s"></param>
        /// <returns></returns>
        public static bool ContainsIgnoreCase(this List<string> list, string s)
        {
            for (int j = 0; j < list.Count; j++)
            {
                if (list[j].Equals(s, StringComparison.OrdinalIgnoreCase))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Moves "up" the first item that passes the criteria determined by function Match
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="Match"></param>
        /// <returns></returns>
        public static bool MoveUp<T>(this List<T> list, Func<T, bool> Match) where T : class
        {
            T temp = null;
            int n = 1;
            for (; n < list.Count; n++)
            {
                if (Match(list[n]))
                {
                    temp = list[n];
                    list.RemoveAt(n);
                    break;
                }
            }
            if (temp != null)
            {
                list.Insert(n - 1, temp);
            }
            return (temp != null);
        }

        /// <summary>
        /// Moves "down" the first item that passes the criteria determined by function Match
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="Match"></param>
        /// <returns></returns>
        public static bool MoveDown<T>(this List<T> list, Func<T, bool> Match) where T : class
        {
            T temp = null;
            int n = 0;
            for (; n < list.Count - 1; n++)
            {
                if (Match(list[n]))
                {
                    temp = list[n];
                    list.RemoveAt(n);
                    break;
                }
            }

            if (temp != null)
            {
                list.Insert(n + 1, temp);
            }
            return (temp != null);
        }

        /// <summary>
        /// Swaps list item at index n1 with item at index n2
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="n1"></param>
        /// <param name="n2"></param>
        public static void Swap<T>(this List<T> list, int n1, int n2) where T : class
        {
            T temp = list[n1];
            list[n1] = list[n2];
            list[n2] = temp;
        }

        /// <summary>
        /// Removes all items from an observable collection that meet criteria test(item) == true, optionally removing only the first found item (find is in reverse order)
        /// Returns true if at least one item was removed, false otherwise
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <param name="onlyFirst"></param>
        /// <param name="test"></param>
        /// <returns></returns>
        public static bool RemoveAll<T>(this ObservableCollection<T> collection, bool onlyFirst, Func<T, bool> test) where T : class
        {
            bool found = false;

            for (int n = collection.Count - 1; n >= 0; n--)
            {
                if (test(collection[n]))
                {
                    found = true;

                    collection.RemoveAt(n);

                    if (onlyFirst)
                        break;
                }
            }

            return found;
        }
        /// <summary>
        /// Removes all items from an observable collection that meet criteria test(item) == true
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="test"></param>
        /// <returns></returns>
        public static ObservableCollection<T> RemoveAll<T>(this ObservableCollection<T> list, Func<T, bool> test)
        {
            if (list == null)
                return null;

            for (int n = list.Count - 1; n >= 0; n--)
            {
                if (test(list[n]))
                {
                    list.RemoveAt(n);
                }
            }

            return list;
        }

        /// <summary>
        /// Given a list of items of type TInterface, creates a list of type TClass containing the same items
        /// </summary>
        /// <typeparam name="TInterface"></typeparam>
        /// <typeparam name="TClass"></typeparam>
        /// <param name="list"></param>
        /// <returns></returns>
        public static List<TClass> ConvertList<TInterface, TClass>(this List<TInterface> list)
            where TInterface : class
            where TClass : class, TInterface
        {
            List<TClass> returnList = new List<TClass>(list.Count);

            for (int n = 0; n < list.Count; n++)
            {
                returnList.Add(list[n] as TClass);
            }

            return returnList;
        }

        /// <summary>
        /// Given an array of items of type TInterface, creates a list of type TClass containing the same items
        /// </summary>
        /// <typeparam name="TInterface"></typeparam>
        /// <typeparam name="TClass"></typeparam>
        /// <param name="items"></param>
        /// <returns></returns>
        public static List<TClass> CreateList<TInterface, TClass>(this TInterface[] items)
            where TInterface : class
            where TClass : class, TInterface
        {
            List<TClass> returnList = new List<TClass>(items.Length);

            for (int n = 0; n < items.Length; n++)
            {
                returnList.Add(items[n] as TClass);
            }

            return returnList;
        }

        /// <summary>
        /// Takes a list of items that implement IProvidesUniqueID and finds the max unique ID
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="items"></param>
        /// <returns></returns>
        public static long MaxID<T>(this List<T> items)
             where T : class, IProvidesUniqueID
        {
            long max = 0;

            foreach (T item in items)
            {
                if (item.UniqueID > max)
                    max = item.UniqueID;
            }

            return max;
        }

        /// <summary>
        /// takes a list of items of TInterface and casts each to TClass.  Returns an array with the same items but of type TClass
        /// </summary>
        /// <typeparam name="TInterface"></typeparam>
        /// <typeparam name="TClass"></typeparam>
        /// <param name="list"></param>
        /// <returns></returns>
        public static TClass[] ToArray<TInterface, TClass>(this List<TInterface> list)
            where TInterface : class
            where TClass : class, TInterface
        {
            TClass[] array = new TClass[list.Count];

            for (int n = 0; n < list.Count; n++)
            {
                array[n] = list[n] as TClass;
            }

            return array;
        }

        /// <summary>
        /// takes an array of items of TInterface and casts each to TClass.  Returns an array with the same items but of type TClass
        /// </summary>
        /// <typeparam name="TInterface"></typeparam>
        /// <typeparam name="TClass"></typeparam>
        /// <param name="items"></param>
        /// <returns></returns>
        public static TClass[] ToArray<TInterface, TClass>(this TInterface[] items)
            where TInterface : class
            where TClass : class, TInterface
        {
            TClass[] array = new TClass[items.Length];

            for (int n = 0; n < items.Length; n++)
            {
                array[n] = items[n] as TClass;
            }

            return array;
        }

        /// <summary>
        /// takes an array of items of TClass and casts each to TInterface.  Returns an array with the same items but of type TInterface
        /// </summary>
        /// <typeparam name="TInterface"></typeparam>
        /// <typeparam name="TClass"></typeparam>
        /// <param name="items"></param>
        /// <returns></returns>
        public static TInterface[] ToInterfaceArray<TInterface, TClass>(this TClass[] items)
            where TInterface : class
            where TClass : class, TInterface
        {
            TInterface[] array = new TInterface[items.Length];

            for (int n = 0; n < items.Length; n++)
            {
                array[n] = items[n] as TInterface;
            }

            return array;
        }

        /// <summary>
        /// Compares a new collection with an old collection, using function parameters getKeyT1 and getKeyT2 to determine similarity
        /// Outputs a list of new items (items in new collection that were not in the old collection, and a list of items no longer present in newCollection
        /// </summary>
        /// <typeparam name="T1"></typeparam>
        /// <param name="newCollection"></param>
        /// <param name="oldCollection"></param>
        /// <param name="getKey"></param>
        /// <param name="newItems"></param>
        /// <param name="noLongerPresent"></param>
        /// <returns></returns>
        public static bool CompareCollections<T1>(this IList<T1> newCollection, IList<T1> oldCollection, Func<T1, int> getKey, out List<T1> newItems, out List<T1> noLongerPresent)
        {
            return CompareCollections<T1, T1>(newCollection, oldCollection, getKey, getKey, out newItems, out noLongerPresent);
        }

        /// <summary>
        /// Compares a new collection with an old collection, using function parameters getKeyT1 and getKeyT2 to determine similarity
        /// Outputs a list of new items (items in new collection that were not in the old collection, and a list of items no longer present in newCollection
        /// </summary>
        /// <typeparam name="T1"></typeparam>
        /// <typeparam name="T2"></typeparam>
        /// <param name="newCollection"></param>
        /// <param name="oldCollection"></param>
        /// <param name="getKeyT1"></param>
        /// <param name="getKeyT2"></param>
        /// <param name="newItems"></param>
        /// <param name="noLongerPresent"></param>
        /// <returns></returns>
        public static bool CompareCollections<T1, T2>(this IList<T1> newCollection, IList<T2> oldCollection, Func<T1, int> getKeyT1, Func<T2, int> getKeyT2, out List<T1> newItems, out List<T2> noLongerPresent)
        {
            List<int> handled = new List<int>();
            newItems = new List<T1>();
            noLongerPresent = new List<T2>();

            foreach (var item1 in newCollection)
            {
                int id1 = getKeyT1(item1);
                bool found = false;

                foreach (var item2 in oldCollection)
                {
                    int id2 = getKeyT2(item2);
                    if (id1 == id2)
                    {
                        found = true;
                        break;
                    }
                }

                if (!found)
                    newItems.Add(item1);

                handled.Add(id1);
            }

            if (oldCollection != null)
            {
                for (int n = oldCollection.Count - 1; n >= 0; n--)
                {
                    var item2 = oldCollection[n];
                    int id2 = getKeyT2(item2);
                    bool found = false;

                    foreach (var id in handled)
                    {
                        if (id == id2)
                        {
                            found = true;
                            break;
                        }
                    }

                    if (!found)
                        noLongerPresent.Add(item2);
                }
            }

            return (newItems.Count == 0 && noLongerPresent.Count == 0);
        }

        /// <summary>
        /// Compares a new collection with an old collection, using object.ReferenceEquals to determine object equality
        /// Outputs a list of new items (items in new collection that were not in the old collection, and a list of items no longer present in newCollection        
        /// </summary>
        /// <typeparam name="T1"></typeparam>
        /// <param name="newCollection"></param>
        /// <param name="oldCollection"></param>
        /// <param name="newItems"></param>
        /// <param name="noLongerPresent"></param>
        /// <returns></returns>
        public static bool CompareCollections<T1>(this IList<T1> newCollection, IList<T1> oldCollection, out List<T1> newItems, out List<T1> noLongerPresent)
        {
            List<T1> handled = new List<T1>();
            newItems = new List<T1>();
            noLongerPresent = new List<T1>();

            foreach (var item1 in newCollection)
            {
                bool found = false;

                foreach (var item2 in oldCollection)
                {
                    if (object.ReferenceEquals(item1, item2))
                    {
                        found = true;
                        break;
                    }
                }

                if (!found)
                    newItems.Add(item1);

                handled.Add(item1);
            }

            if (oldCollection != null)
            {
                for (int n = oldCollection.Count - 1; n >= 0; n--)
                {
                    var item2 = oldCollection[n];
                    bool found = false;

                    foreach (var o in handled)
                    {
                        if (object.ReferenceEquals(o, item2))
                        {
                            found = true;
                            break;
                        }
                    }

                    if (!found)
                        noLongerPresent.Add(item2);
                }
            }

            return (newItems.Count == 0 && noLongerPresent.Count == 0);
        }

        /// <summary>
        /// For each item in the enumerable collection, perform the specified action on the item
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="items"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static IEnumerable<T> ForEach<T>(this IEnumerable<T> items, Action<T> action)
        {
            foreach (T item in items)
            {
                action(item);
            }

            return items;
        }

        /// <summary>
        /// Perform an action on each member of the observable collection, navigating the collection backwards
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static ObservableCollection<T> ForEachBackwards<T>(this ObservableCollection<T> list, Action<T> action)
        {
            for (int n = list.Count - 1; n >= 0; n--)
            {
                action(list[n]);
            }

            return list;
        }

        public static IEnumerable<T> ForEachIfNotNull<T>(this IEnumerable<T> items, Action<T> action)
        {
            if (items == null)
                return null;

            foreach (T item in items)
                action(item);

            return items;
        }

        /// <summary>
        /// If items is null, returns default(T)
        /// If first item of the list is not a list, return the first item in the list (cast to type T)
        /// If first item of the list is a list of type T, return the first item of the items[0] inner list 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="items"></param>
        /// <returns></returns>
        public static T Grab<T>(this IList items)
        {
            if (items == null)
                return default(T);

            IList<T> inner = items[0] as IList<T>;

            if (inner == null)
                return (T)items[0];

            return inner[0];
        }

        /// <summary>
        /// Removes all duplicate items (equality determined using ==)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="items"></param>
        public static void RemoveDuplicates<T>(this List<T> items)
             where T : class
        {
            for (int n = 0; n < items.Count; n++)
            {
                for (int j = items.Count - 1; j > n; j--)
                {
                    if (items[n] == items[j])
                    {
                        items.RemoveAt(j);
                    }
                }
            }
        }

        public static void RemoveDuplicates<T>(this List<T> items, Func<T, T, bool> equality)
             where T : class
        {
            for (int n = 0; n < items.Count; n++)
            {
                for (int j = items.Count - 1; j > n; j--)
                {
                    if (equality(items[n], items[j]))
                    {
                        items.RemoveAt(j);
                    }
                }
            }
        }

        /// <summary>
        /// Determines if item is in the set of items (equality determined using the object.Equals(object) method)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="item"></param>
        /// <param name="setOfItems"></param>
        /// <returns></returns>
        public static bool IsInSet<T>(this T item, params T[] setOfItems)
        {
            for (int n = 0; n < setOfItems.Length; n++)
            {
                if (item.Equals(setOfItems[n]))
                    return true;
            }
            return false;
        }

    }
}
